#!/usr/bin/env bash
set -eux

### --start_docs

## Setup devstack-gate's vxlan networking for multinode jobs
## =========================================================

## .. note::
##   The following steps are needed:
##   * Source in common environment (deploy.env)
##   * Clone devstack and devstack-gate for necessary scripts
##   * Create the vxlan bridge
##   * SSH into subnodes, bring the bridge device up on them
##   * Perform a ping check to see communication is possible over the bridge

## Prepare Your Environment
## ------------------------

## * Source tripleo-ci's deploy.env environment variables (if available)
## ::

if [ -f "{{ tripleo_root }}/tripleo-ci/deploy.env" ]; then
    source {{ tripleo_root }}/tripleo-ci/deploy.env
fi

## * Use the clone() function from tripleo-ci/scripts/tripleo.sh
## ::

function clone {

    local repo=$1

    echo "$0 requires $repo to be cloned at \$TRIPLEO_ROOT ({{ tripleo_root }})"

    mkdir -p {{ tripleo_root }}
    if [ ! -d {{ tripleo_root }}/$(basename $repo) ]; then
        echo "$repo not found at {{ tripleo_root }}/$repo, git cloning."
        pushd {{ tripleo_root }}
        git clone https://opendev.org/$repo
        popd
    else
        echo "$repo found at {{ tripleo_root }}/$repo, nothing to do."
    fi

}

# This function creates an internal gre bridge to connect all external
# network bridges across the compute and network nodes.
# bridge_name: Bridge name on each host for logical l2 network
#              connectivity.
# host_ip: ip address of the bridge host which is reachable for all peer
#          the hub for all of our spokes.
# set_ips: Whether or not to set l3 addresses on our logical l2 network.
#          This can be helpful for setting up routing tables.
# offset: starting value for gre tunnel key and the ip addr suffix
# The next two parameters are only used if set_ips is "True".
# pub_addr_prefix: The IPv4 address three octet prefix used to give compute
#                  nodes non conflicting addresses on the pub_if_name'd
#                  network. Should be provided as X.Y.Z. Offset will be
#                  applied to this as well as the below mask to get the
#                  resulting address.
# pub_addr_mask: the CIDR mask less the '/' for the IPv4 addresses used
#                above.
# every additional parameter is considered as a peer host (spokes)
#
# For OVS troubleshooting needs:
#   http://www.yet.org/2014/09/openvswitch-troubleshooting/
#
function ovs_vxlan_bridge {
    if is_suse; then
        local ovs_package='openvswitch'
        local ovs_service='openvswitch'
    elif is_fedora; then
        local ovs_package='openvswitch openstack-selinux'
        local ovs_service='openvswitch'
    elif uses_debs; then
        local ovs_package='openvswitch-switch'
        local ovs_service='openvswitch-switch'
    else
        echo "Unsupported platform, can't determine openvswitch service"
        exit 1
    fi
    local install_ovs_deps="source {{ tripleo_root }}/devstack/functions-common; \
                            install_package ${ovs_package}; \
                            restart_service ${ovs_service}"
    local mtu=1450
    local bridge_name=$1
    local host_ip=$2
    local set_ips=$3
    local offset=$4
    if [[ "$set_ips" == "True" ]] ; then
        local pub_addr_prefix=$5
        local pub_addr_mask=$6
        shift 6
    else
        shift 4
    fi
    local peer_ips=$@
    # neutron uses 1:1000 with default devstack configuration, avoid overlap
    local additional_vni_offset=1000000
    eval $install_ovs_deps
    # create a bridge, just like you would with 'brctl addbr'
    # if the bridge exists, --may-exist prevents ovs from returning an error
    sudo ovs-vsctl --may-exist add-br $bridge_name
    # as for the mtu, look for notes on lp#1301958 in devstack-vm-gate.sh
    sudo ip link set mtu $mtu dev $bridge_name
    if [[ "$set_ips" == "True" ]] ; then
        echo "Set bridge: ${bridge_name}"
        if ! sudo ip addr show dev ${bridge_name} | grep -q \
            ${pub_addr_prefix}.${offset}/${pub_addr_mask} ; then
                sudo ip addr add ${pub_addr_prefix}.${offset}/${pub_addr_mask} \
                    dev ${bridge_name}
        fi
    fi
    sudo ip link set dev $bridge_name up
    for node_ip in $peer_ips; do
        offset=$(( offset+1 ))
        vni=$(( offset + additional_vni_offset ))
        # For reference on how to setup a tunnel using OVS see:
        #   http://openvswitch.org/support/config-cookbooks/port-tunneling/
        # The command below is equivalent to the sequence of ip/brctl commands
        # where an interface of vxlan type is created first, and then plugged into
        # the bridge; options are command specific configuration key-value pairs.
        #
        # Create the vxlan tunnel for the Controller/Network Node:
        #  This establishes a tunnel between remote $node_ip to local $host_ip
        #  uniquely identified by a key $offset
        sudo ovs-vsctl --may-exist add-port $bridge_name \
            ${bridge_name}_${node_ip} \
            -- set interface ${bridge_name}_${node_ip} type=vxlan \
            options:remote_ip=${node_ip} \
            options:key=${vni} \
            options:local_ip=${host_ip}
        # Now complete the vxlan tunnel setup for the Compute Node:
        #  Similarly this establishes the tunnel in the reverse direction
        remote_command $node_ip "$install_ovs_deps"
        remote_command $node_ip sudo ovs-vsctl --may-exist add-br $bridge_name
        remote_command $node_ip sudo ip link set mtu $mtu dev $bridge_name
        remote_command $node_ip sudo ovs-vsctl --may-exist add-port $bridge_name \
            ${bridge_name}_${host_ip} \
            -- set interface ${bridge_name}_${host_ip} type=vxlan \
            options:remote_ip=${host_ip} \
            options:key=${vni} \
            options:local_ip=${node_ip}
        if [[ "$set_ips" == "True" ]] ; then
            if ! remote_command $node_ip sudo ip addr show dev ${bridge_name} | \
                grep -q ${pub_addr_prefix}.${offset}/${pub_addr_mask} ; then
                    remote_command $node_ip sudo ip addr add \
                        ${pub_addr_prefix}.${offset}/${pub_addr_mask} \
                        dev ${bridge_name}
            fi
        fi
        remote_command $node_ip sudo ip link set dev $bridge_name up
    done
}

## Perform the Bridge Creation
## ---------------------------

## * Clone devstack and devstack-gate repositories for network setup scripts
## ::

clone openstack/devstack
clone openstack/devstack-gate

## * Source in devstack-gate's common utility functions
## ::

echo "Sourcing devstack-gate/functions.sh"
set +u
source {{ tripleo_root }}/devstack-gate/functions.sh
set -u

primary_node=$(cat /etc/nodepool/primary_node_private)
sub_nodes=$(cat /etc/nodepool/sub_nodes_private)

# Create OVS vxlan bridges
# If br-ctlplane already exists on this node, we need to bring it down
# first, then bring it back up after calling ovs_vxlan_bridge. This ensures
# that the route added to br-ex by ovs_vxlan_bridge will be preferred over
# the br-ctlplane route. If it's not preferred, multinode connectivity
# across the vxlan bridge will not work.
if [ -f /etc/sysconfig/network-scripts/ifcfg-br-ctlplane ]; then
    sudo ifdown br-ctlplane
fi

## * Set up the vxlan bridge to the subnode(s)
## ::

set +u
echo "Running ovs_vxlan_bridge"
ovs_vxlan_bridge {{ undercloud_local_interface }} $primary_node "True" 2 {{ vxlan_networking_addr_prefix }} {{ vxlan_networking_addr_netmask }} $sub_nodes
set -u

echo "Setting {{ undercloud_local_interface }} up on $primary_node"
sudo ip link set dev {{ undercloud_local_interface }} up
sudo ip link set dev {{ undercloud_local_interface }} mtu {{ vxlan_mtu }}
echo "MTU={{ vxlan_mtu }}" | sudo tee -a /etc/sysconfig/network-scripts/ifcfg-{{ undercloud_local_interface }}

if [ -f /etc/sysconfig/network-scripts/ifcfg-br-ctlplane ]; then
    sudo ifup br-ctlplane
    sudo ip link set dev br-ctlplane mtu {{ vxlan_mtu }}
    echo "MTU={{ vxlan_mtu }}" | sudo tee -a /etc/sysconfig/network-scripts/ifcfg-br-ctlplane
fi

## * Restart neutron-openvswitch-agent if it's enabled, since it may have
##   terminated when br-ctlplane was down
## ::

if [ "$(sudo systemctl is-enabled neutron-openvswitch-agent)" = 'enabled' ]; then
    sudo systemctl reset-failed neutron-openvswitch-agent
    sudo systemctl restart neutron-openvswitch-agent
fi

### --stop_docs
